﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Nomo.CoreModule;

namespace Nomo.TcpModule
{
    /// <inheritdoc />
    public class TcpSocket : ITcpSocket
    {
        private readonly List<byte[]>  _unpackedBytesList;                             // 用于存放解包后数据的序列。
        private readonly UtilityStream _stream;                                        // 用于存储数据的流。
        private readonly object        _lock             = new object();               // 同步锁。
        private const    int           DefaultBufferSize = 1 << 16;                    // 默认缓存容量（65536）.
        private const    int           MaxBufferSize     = 1 << (sizeof(int) * 8 - 2); // 最大缓存容量（1073741824）.

        /// <inheritdoc />
        public IPacker Packer { get; private set; }

        /// <inheritdoc />
        public IUnpacker Unpacker { get; private set; }

        /// <inheritdoc />
        public Socket Socket { get; private set; }

        /// <inheritdoc />
        public event Action Connecting;

        /// <inheritdoc />
        public event Action<bool> Connected;

        /// <inheritdoc />
        public event Action<int> ReceivedRawBytes;

        /// <inheritdoc />
        public event Action<List<byte[]>> ReceivedUnpackedBytes;

        /// <inheritdoc />
        public event Action<int> SendingRawBytes;

        /// <inheritdoc />
        public event Action<byte[]> SendingPackedBytes;

        /// <inheritdoc />
        public event Action Disconnected;

        /// <inheritdoc />
        public event Action Disposing;

        /// <summary>
        /// 使用指定的缓存容量、封包器和解包器构建一个 TCP 套接字。
        /// </summary>
        /// <param name="bufferSize">缓存容量。</param>
        /// <param name="packer">封包器。</param>
        /// <param name="unpacker">解包器。</param>
        public TcpSocket(int bufferSize = DefaultBufferSize, IPacker packer = null, IUnpacker unpacker = null)
        {
            bufferSize         = Math.Min(bufferSize, MaxBufferSize);
            _stream            = new UtilityStream(bufferSize);
            Packer             = packer   ?? new RawPacker();
            Unpacker           = unpacker ?? new RawUnpacker();
            _unpackedBytesList = new List<byte[]>();
        }

        /// <summary>
        /// 是否已连接？
        /// </summary>
        public bool IsConnected => Socket != null && Socket.Connected;

        /// <inheritdoc />
        public void Connect(IPEndPoint endPoint)
        {
            lock (_lock)
            {
                Connecting?.Invoke();
                try
                {
                    Socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                }
                catch (Exception e)
                {
                    throw new Exception(e.ToString());
                }
                try
                {
                    Socket.BeginConnect(endPoint, EndConnect, Socket);
                }
                catch (Exception e)
                {
                    throw new Exception(e.ToString());
                }
            }
        }

        private void EndConnect(IAsyncResult ar)
        {
            try
            {
                ((Socket) ar.AsyncState).EndConnect(ar);

                if (!IsConnected)
                {
                    Connected?.Invoke(false);
                    return;
                }

                Connected?.Invoke(true);
                Packer.Init();
                Unpacker.Init();
                Receive();
            }
            catch (Exception e)
            {
                throw new Exception(e.ToString());
            }
        }

        /// <inheritdoc />
        public void Send(byte[] bytes)
        {
            lock (_lock)
            {
                if (bytes == null)
                {
                    return;
                }

                try
                {
                    SendingRawBytes?.Invoke(bytes.Length);
                    Packer.Pack(ref bytes);
                    SendingPackedBytes?.Invoke(bytes);
                    if (bytes != null)
                    {
                        Socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, EndSend, Socket);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
            }
        }

        private void EndSend(IAsyncResult ar)
        {
            lock (_lock)
            {
                if (!IsConnected)
                {
                    return;
                }
                try
                {
                    ((Socket) ar.AsyncState).EndSend(ar);
                }
                catch (Exception e)
                {
                    throw new Exception(e.ToString());
                }
            }
        }

        private void Receive()
        {
            lock (_lock)
            {
                if (!IsConnected)
                {
                    return;
                }
                try
                {
                    ref readonly var buffer = ref _stream.BufferReadonlyReference;
                    Socket.BeginReceive(buffer, 0, (int) _stream.Length, SocketFlags.None, EndReceive, Socket);
                }
                catch (Exception e)
                {
                    throw new Exception(e.ToString());
                }
            }
        }

        private void EndReceive(IAsyncResult ar)
        {
            lock (_lock)
            {
                if (!IsConnected)
                {
                    return;
                }
                int length;
                try
                {
                    length = ((Socket) ar.AsyncState).EndReceive(ar);
                }
                catch (Exception e)
                {
                    throw new Exception(e.ToString());
                }

                ReceivedRawBytes?.Invoke(length);
                var bytes = new byte[length];
                _stream.Read(bytes, 0, bytes.Length);
                _stream.Position = 0;
                _unpackedBytesList.Clear();
                _unpackedBytesList.Add(bytes);
                Unpacker.Unpack(in _unpackedBytesList);
                ReceivedUnpackedBytes?.Invoke(_unpackedBytesList);

                if (length > 0)
                {
                    Receive();
                }
                else
                {
                    Disconnected?.Invoke();
                }
            }
        }

        /// <inheritdoc />
        public void Disconnect()
        {
            try
            {
                Socket.Shutdown(SocketShutdown.Both);
                Socket.Close();
            }
            catch (Exception e)
            {
                throw new Exception(e.ToString());
            }
            Disconnected?.Invoke();
        }

        /// <inheritdoc />
        public void Dispose()
        {
            lock (_lock)
            {
                Disposing?.Invoke();
                var isConnected = IsConnected;
                try
                {
                    Socket.Shutdown(SocketShutdown.Both);
                    Socket.Close();
                }
                catch (Exception e)
                {
                    throw new Exception(e.ToString());
                }
                if (isConnected)
                {
                    Disconnected?.Invoke();
                }

                Socket                = null;
                Packer                = null;
                Unpacker              = null;
                Connecting            = null;
                Connected             = null;
                ReceivedRawBytes      = null;
                ReceivedUnpackedBytes = null;
                SendingRawBytes       = null;
                SendingPackedBytes    = null;
                Disconnected          = null;
                Disposing             = null;
            }
        }
    }
}
